/***************************************************************************
 *   Copyright (C) 2008 by Chris Bornholdt   *
 *   chris.bornholdt@gmail.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "threadmanager.hpp"

#include <QMutex>
#include <QtDebug>
#include <cassert>

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/lambda/construct.hpp>

using namespace boost::lambda;


ThreadManager::ActionId ThreadManager::actionIdCounter_ = 0;

ThreadObject::ThreadObject() : complete_(true) {}

ThreadObject::~ThreadObject(){}

void ThreadObject::apply(tr1::function<void (QVariant &)> action)
{
	if(isRunning())
		throw 3;	//TODO throw something meaningful
		
	action_ = action;
	var_.clear();
	complete_ = false;
	start();
}

QVariant ThreadObject::variant() const
{	return var_;	}

void ThreadObject::clearVariant()	{	var_.clear();	}

bool ThreadObject::isComplete() const
{	return complete_;	}

void ThreadObject::setComplete()
{	complete_ = true;	}

void ThreadObject::run()
{	action_(var_);	}

ThreadManager *globalThreadManager = 0;

ThreadManager::ThreadManager(QObject *parent, ThreadManager::WarningLevel warningLevel, 
	int desiredThreads) : QObject(parent), mutex_(new QMutex(QMutex::Recursive)), runningThreads_(0), 
		desiredThreadCount_(desiredThreads == 0 ? QThread::idealThreadCount() : desiredThreads),
		warningLevel_(warningLevel)
{	globalThreadManager = this;	}

ThreadManager::~ThreadManager()	//delete all ThreadObjects
{
	delete mutex_;
	std::for_each(threadObjects_.begin(), threadObjects_.end(), 
		bind(delete_ptr(), _1));
}

ThreadManager& ThreadManager::instance()
{	
	assert(globalThreadManager != (ThreadManager *) 0);
	return *globalThreadManager;
}

void ThreadManager::updateRunningActions()
{
	QMutexLocker lock(mutex_);
	
	//runningThreads_ = std::count_if(threadObjects_.begin(), threadObjects_.end(), 
	//	bind(&ThreadObject::isRunning, _1));
		
	if(runningThreads_ < desiredThreadCount_)
		startNewAction();
}
		
void ThreadManager::updateThreadData()	//called by QThread finished/terminated
{
	QMutexLocker lock(mutex_);

	ThreadObject* s = qobject_cast<ThreadObject *>(sender());
	
	if(!s)
		return;
		
//	qDebug() << s->variant() << "update";
		
	QList<ActionData>::iterator iter = std::find_if(actionDataList_.begin(), 
		actionDataList_.end(), bind(&ActionData::thread, _1) == s);
	if(iter != actionDataList_.end())	//found the actionData
	{
		if(s->variant().isValid())	//ensure thread finished and was not terminated
			actionFinished(*iter);
		actionDataList_.erase(iter);
		
		s->setComplete();
		--runningThreads_;
	}
		
	updateRunningActions();
}

void ThreadManager::startNewAction()
{
	QMutexLocker lock(mutex_);
		
	QList<ActionData>::iterator iter = std::find_if(actionDataList_.begin(), actionDataList_.end(), 
		bind(&ActionData::thread, _1) == (ThreadObject *) 0);
	//QList<ActionData>::iterator iter = std::partition(actionDataList_.begin(), actionDataList_.end(), 
	//	bind(&ActionData::thread, _1) == (ThreadObject *) 0);
	
	if(iter == actionDataList_.end())	//no waiting actions
		return;	
	
	QList<ActionData>::iterator maxPriorityAction = iter;
	Priority highest = iter->priority;
	while(iter != actionDataList_.end())
	{
		if(highest < iter->priority)
		{
			highest = iter->priority;
			maxPriorityAction = iter;
		}
		iter = std::find_if(++iter, actionDataList_.end(), 
			bind(&ActionData::thread, _1) == (ThreadObject *) 0);
	}
	iter = maxPriorityAction;	
		
	if(runningThreads_ == threadObjects_.size())	//add new ThreadObject to pool
	{
		threadObjects_.append(new ThreadObject());
		connect(threadObjects_.back(), SIGNAL(finished()), this, SLOT(updateThreadData()));
		connect(threadObjects_.back(), SIGNAL(terminated()), this, SLOT(updateThreadData()));
	}
	
	//find first ThreadObject not running, then start it	
	QList<ThreadObject *>::iterator iter2 = std::find_if(threadObjects_.begin(), 
		threadObjects_.end(), 
		bind(&ThreadObject::isComplete, *_1) == true);	/*	I think this is where the issue is	*/
	assert(iter2 != threadObjects_.end());
	iter->thread = *iter2;
	++runningThreads_;
	
	(*iter2)->apply(iter->action);
//	qDebug() << "starting";
}

void ThreadManager::actionFinished(ActionData& action)
{
	QMutexLocker lock(mutex_);
	QVariant result = action.thread->variant();
	action.thread->clearVariant();	//clear immediately
	
	QGenericArgument arg1 = result.isValid() ? 
		QGenericArgument(result.typeName(), result.data()) :
		QGenericArgument();
	QGenericArgument arg2 = action.data.isValid() ?
		QGenericArgument(action.data.typeName(), action.data.data()) :
		QGenericArgument();
		
//	qDebug() << "finished" << result << action.data;
	if(!QMetaObject::invokeMethod(action.reciever, action.method, Qt::QueuedConnection, arg1, arg2))
	{
		switch(warningLevel_)
		{
			case NoWarning :
				return;
			case Warning :
				qWarning() << "failed to connect to method" << action.method << action.data.typeName();
				break;
			case Debug :
				qDebug() << "failed to connect to method" << action.method << action.data.typeName();
				break;
		};
	}
}
